package org.fioma.tree;

import java.io.BufferedWriter;
import java.io.IOException;

import org.fioma.tree.bytes.Tools;

public abstract class SimpleInternalNode<L extends Label<L>, I extends SimpleInternalNode<L, I, F>, F extends SimpleLeaf<L, I, F>> extends SimpleNode<L, I, F>
		implements InternalNode<L, I, F> {

	private Node<L, I, F>[] nodes;

	public SimpleInternalNode(I parent, L label) {
		this(parent, label, new Node[4]);
	}

	public SimpleInternalNode(I parent, L label, Node<L, I, F>[] nodes) {
		super(parent, label);
		this.nodes = nodes;
	}

	protected Node<L, I, F> getChild(byte letter) {
		return nodes[letter];
	}

	protected void setChild(byte letter, Node<L, I, F> node) {
		nodes[letter] = node;
	}

	protected Node<L, I, F>[] getChilds() {
		return nodes;
	}

	@Override
	public Node<L, I, F> nextNode(char letter) {
		return nextNode(Tools.transformToByte(letter));
	}

	@Override
	public Node<L, I, F> nextNode(byte letter) {
		return getChild(letter);
	}

	@Override
	public void setChild(Node<L, I, F> node, char letter) {
		setChild(node, Tools.transformToByte(letter));
	}

	@Override
	public void setChild(Node<L, I, F> node, byte letter) {
		setChild(letter, node);
	}

	@Override
	public boolean isLeaf() {
//		for (Node<L, I, F> node : getChilds()) {
//			if (node != null) {
//				return false;
//			}
//		}
		return false;
	}

	@Override
	public I getOrCreateInternalNode(L word) {
		byte letterNextNode = word.getByte(0);
		Node<L, I, F> nextNode = nextNode(letterNextNode);
		L l = nextNode.getLabel();
		int mismatch = l.getMinsmatch(word);
		if (mismatch == -1) {
			return (I) nextNode;
		} else {
			if (mismatch < l.getLength()) {
				if (mismatch != word.getLength()) {
					throw new RuntimeException("Internal node doesn't exist.");
				} else {
					return createChild(mismatch, letterNextNode);
				}
			} else {
				return ((I) nextNode).getOrCreateInternalNode(word.substring(mismatch));
			}
		}
	}

	// create new internal node in the position pos
	protected I createChild(int pos, byte letter) {
		Node<L, I, F> nextNode = nextNode(letter);
		L labelNextNode = nextNode.getLabel();
		I ret = createChildInternalNode(labelNextNode.substring(0, pos));
		nextNode.setLabel(labelNextNode.substring(pos));
		nextNode.setParent(ret);
		ret.setChild(nextNode, nextNode.getLabel().getByte(0));
		this.setChild(ret, letter);
		return ret;

	}

	// only create the object
	protected abstract I createChildInternalNode(L substring);

	// only create the object
	protected abstract F createChildLeaf(L word);

	@Override
	public F addWord(L word) {
		if (word != null && word.getLength() > 0) {
			byte firstLetter = word.getByte(0);
			Node<L, I, F> nextNode = this.nextNode(firstLetter);
			if (nextNode == null) {
				F ret = this.createChildLeaf(word);
				this.setChild(ret, firstLetter);
				return ret;
			} else {
				L nextWord = nextNode.getLabel();
				int mismatch = word.getMinsmatch(nextWord);
				if (mismatch >= 0 && mismatch < word.getLength()) {
					if (mismatch == nextWord.getLength()) {
						// next word will not be splitted and a new node will not be created in this section
						if (nextNode.isLeaf()) {
							nextNode.setLabel(word);
							return (F) nextNode;
						} else {
							return ((I) nextNode).addWord(word.substring(mismatch));
						}
					} else {
						// new internal node must be created
						I newInternalNode = createChild(mismatch, firstLetter);
						F ret = newInternalNode.createChildLeaf(word.substring(mismatch));
						newInternalNode.setChild(ret, firstLetter);
						return ret;
					}
				} else {
					// word already in the tree
					return null;
				}
			}
		} else {
			return null;
		}
	}

	@Override
	public void printWords(BufferedWriter bw, String prefix) throws IOException {
		for (Node<L, I, F> nextNode : getChilds()) {
			if (nextNode != null) {
				if (nextNode.isLeaf()) {
					bw.write(prefix);
					bw.write(nextNode.getLabel().toString());
					bw.newLine();
				} else {
					((I) nextNode).printWords(bw, prefix + getLabel().toString());
				}
			}
		}
	}

	@Override
	public boolean find(L word) {
		if (word == null || word.getLength() == 0) {
			return true;
		} else {
			byte firstLetter = word.getByte(0);
			Node<L, I, F> nextNode = nextNode(firstLetter);
			if (nextNode == null) {
				return false;
			} else {
				L nextWord = nextNode.getLabel();
				int mismatch = word.getMinsmatch(nextWord);
				if (mismatch >= 0 && mismatch < word.getLength()) {
					if (mismatch == nextWord.getLength()) {
						if (nextNode.isLeaf()) {
							return false;
						} else {
							return ((InternalNode<L, I, F>) nextNode).find(word.substring(mismatch));
						}
					} else {
						return false;
					}
				} else {
					return true;
				}
			}
		}
	}

}
